[NOIP2019]Emiya家今天的饭

2019-11-22
NOIP

题意

给出$n\cdot m$的矩阵,从中选出任意个数$(\geq 0)$的元素,使得每行最多只有1个,且每列的个数不超过总数的一半

求所有方案中元素的乘积的和,对$998244353$取模

题解

首先,合法的方案数等于所有方案减去不满足的方案数

对于不满足的情况,一定有且仅有一列的选择个数超过一半

若选了$x$道菜,其中某种食材用了$y$,条件变形一下得到:$2y+n-x\geq n$

枚举超过一半的那一列,令$f[j][k]$表示有j行取了数,目前$2y+n-x$为k的方案数

分为当前行不选;当前行选,不选选定的列;选当前行,选选定的列 进行Dp即可

调试记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <cstdio>
#include <cstring>
#define LL long long
const int maxn = 2005;
const int mo = 998244353;
using namespace std;
int n, m, a[105][maxn], s[105];
LL ans = 1, f[105][maxn];
int main(){
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++){
scanf("%d", a[i] + j); s[i] = (s[i] + a[i][j]) % mo;
if (j == m) ans = 1ll * ans * (s[i] + 1) % mo;
} ans--;
for (int i = 1; i <= m; i++){
memset(f, 0, sizeof f); f[0][0] = 1;
for (int j = 1; j <= n; j++){
for (int k = 0; k <= (n << 1); k++){
f[j][k] = (f[j][k] + 1ll * f[j - 1][k] * (s[j] + mo - a[j][i]) % mo) % mo;
f[j][k + 1] = (f[j][k + 1] + f[j - 1][k]) % mo;
f[j][k + 2] = (f[j][k + 2] + 1ll * f[j - 1][k] * a[j][i] % mo) % mo;
}
}
for (int j = n + 1; j <= (n << 1); j++) ans = (ans + mo - f[n][j]) % mo;
}
printf("%lld\n", ans);
return 0;
}